﻿using System;
using System.Linq;
using System.Reflection;
using Properties;

namespace BorgExt
{
    public class Qualifier : RuntimeElement
    {
        private static readonly Type[] SlotEventHandlerParamTypes = new[] {typeof (SlotEventArgs)};
        private static readonly Type[] CompositionEventHandlerParamTypes = new[] {typeof (CompositionEventArgs)};
        private static readonly Type[] ExtensionEventHandlerParamTypes = new[] {typeof (ExtensionEventArgs)};
        private static readonly Type[] ConstructorParamTypes = new[] {typeof (Extension)};

        private const BindingFlags EventHandlerBindingFlags =
            BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

        private const BindingFlags ConstructorBindingFlags =
            BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

        private const QualificationState ContractErrorState =
            QualificationState.QualificationMissing | QualificationState.ContractAssemblyCouldNotBeLoaded |
            QualificationState.ContractNoSlotDefinitions;

        private const QualificationState SlotDefinitionErrorState =
            QualificationState.QualificationMissing | QualificationState.SlotDefinitionContractNotQualified |
            QualificationState.SlotDefinitionAlreadyExists | QualificationState.SlotDefinitionTypeNotInContractAssembly;

        private const QualificationState ParamDefinitionErrorState =
            QualificationState.QualificationMissing | QualificationState.ParamDefinitionSlotDefinitionNotQualified |
            QualificationState.ParamDefinitionAlreadyExists | QualificationState.ParamDefinitionTypeNotFound |
            QualificationState.ParamDefinitionDefaultValueNotAssignable;

        private const QualificationState PluginErrorState =
            QualificationState.QualificationMissing | QualificationState.PluginAssemblyCouldNotBeLoaded |
            QualificationState.PluginNoExtensions;

        private const QualificationState ExtensionTypeErrorState =
            QualificationState.QualificationMissing | QualificationState.ExtensionPluginNotQualified |
            QualificationState.ExtensionTypeNotInPluginAssembly | QualificationState.ExtensionInvalidConstructor |
            QualificationState.ExtensionNoPlugs;

        private const QualificationState PlugTypeErrorState =
            QualificationState.QualificationMissing | QualificationState.PlugExtensionNotQualified |
            QualificationState.PlugNameAlreadyExists | QualificationState.PlugSlotDefinitionNotFound |
            QualificationState.PlugInterfaceNotImplemented | QualificationState.PlugParamMissing |
            QualificationState.PlugParamNotQualified;

        private const QualificationState ParamErrorState =
            QualificationState.QualificationMissing | QualificationState.ParamPlugNotQualified |
            QualificationState.ParamNameAlreadyExists | QualificationState.ParamNotAssignable;

        private const QualificationState SlotTypeErrorState =
            QualificationState.QualificationMissing | QualificationState.SlotExtensionNotQualified |
            QualificationState.SlotNameAlreadyExists | QualificationState.SlotSlotDefinitionNotFound;

        static Qualifier()
        { }

        protected internal Qualifier()
        { }

        public bool Qualify(Contract contract)
        {
            if (contract == null)
                throw new ArgumentNullException("contract");
            
            return InternalQualify(contract);
        }

        public bool Qualify(SlotDefinition slotDefinition)
        {
            if (slotDefinition == null)
                throw new ArgumentNullException("slotDefinition");
            
            return InternalQualify(slotDefinition);
        }

        public bool Qualify(ParamDefinition paramDefinition)
        {
            if (paramDefinition == null)
                throw new ArgumentNullException("paramDefinition");
            
            return InternalQualify(paramDefinition);
        }

        public bool Qualify(Plugin plugin)
        {
            if (plugin == null)
                throw new ArgumentNullException("plugin");
            
            return InternalQualify(plugin);
        }

        public bool Qualify(ExtensionType extensionType)
        {
            if (extensionType == null)
                throw new ArgumentNullException("extensionType");
            
            return InternalQualify(extensionType);
        }

        public bool Qualify(PlugType plugType)
        {
            if (plugType == null)
                throw new ArgumentNullException("plugType");
            
            return InternalQualify(plugType);
        }

        public bool Qualify(Param param)
        {
            if (param == null)
                throw new ArgumentNullException("param");
            
            return InternalQualify(param);
        }

        public bool Qualify(SlotType slotType)
        {
            if (slotType == null)
                throw new ArgumentNullException("slotType");
            
            return InternalQualify(slotType);
        }

        public bool IsQualified(Qualifiable qualifiable)
        {
            if (qualifiable == null)
                throw new ArgumentNullException("qualifiable");

            if (qualifiable is Contract)
                return InternalIsQualified((Contract) qualifiable);

            if (qualifiable is SlotDefinition)
                return InternalIsQualified((SlotDefinition) qualifiable);
            
            if (qualifiable is ParamDefinition)
                return InternalIsQualified((ParamDefinition) qualifiable);
            
            if (qualifiable is Plugin)
                return InternalIsQualified((Plugin) qualifiable);
            
            if (qualifiable is ExtensionType)
                return InternalIsQualified((ExtensionType) qualifiable);
            
            if (qualifiable is PlugType)
                return InternalIsQualified((PlugType) qualifiable);
            
            if (qualifiable is Param)
                return InternalIsQualified((Param) qualifiable);
            
            if (qualifiable is SlotType)
                return InternalIsQualified((SlotType) qualifiable);
            
            throw new ArgumentException("Type not supported");
        }

        public bool IsQualified(Contract contract)
        {
            if (contract == null)
                throw new ArgumentNullException("contract");
            
            return InternalIsQualified(contract);
        }

        public bool IsQualified(SlotDefinition slotDefinition)
        {
            if (slotDefinition == null)
                throw new ArgumentNullException("slotDefinition");
            
            return InternalIsQualified(slotDefinition);
        }

        public bool IsQualified(ParamDefinition paramDefinition)
        {
            if (paramDefinition == null)
                throw new ArgumentNullException("paramDefinition");
            
            return InternalIsQualified(paramDefinition);
        }

        public bool IsQualified(Plugin plugin)
        {
            if (plugin == null)
                throw new ArgumentNullException("plugin");
            
            return InternalIsQualified(plugin);
        }

        public bool IsQualified(ExtensionType extensionType)
        {
            if (extensionType == null)
                throw new ArgumentNullException("extensionType");
            
            return InternalIsQualified(extensionType);
        }

        public bool IsQualified(PlugType plugType)
        {
            if (plugType == null)
                throw new ArgumentNullException("plugType");
            
            return InternalIsQualified(plugType);
        }

        public bool IsQualified(Param param)
        {
            if (param == null)
                throw new ArgumentNullException("param");
            
            return InternalIsQualified(param);
        }

        public bool IsQualified(SlotType slotType)
        {
            if (slotType == null)
                throw new ArgumentNullException("slotType");
            
            return InternalIsQualified(slotType);
        }

        public bool IsQualificationMissing(Qualifiable qualifiable)
        {
            if (qualifiable == null)
                throw new ArgumentNullException("qualifiable");
            
            return InternalIsQualificationMissing(qualifiable);
        }

        internal bool InternalIsQualified(Contract contract)
        {
            return (contract.QualificationState &
                    (QualificationState.QualificationMissing | QualificationState.ContractAssemblyCouldNotBeLoaded |
                     QualificationState.ContractNoSlotDefinitions)) == QualificationState.None;
        }

        internal bool InternalIsQualified(SlotDefinition slotDefinition)
        {
            return (slotDefinition.QualificationState &
                    (QualificationState.QualificationMissing | QualificationState.SlotDefinitionContractNotQualified |
                     QualificationState.SlotDefinitionAlreadyExists |
                     QualificationState.SlotDefinitionTypeNotInContractAssembly)) == QualificationState.None;
        }

        internal bool InternalIsQualified(ParamDefinition paramDefinition)
        {
            return (paramDefinition.QualificationState &
                    (QualificationState.QualificationMissing |
                     QualificationState.ParamDefinitionSlotDefinitionNotQualified |
                     QualificationState.ParamDefinitionAlreadyExists | QualificationState.ParamDefinitionTypeNotFound |
                     QualificationState.ParamDefinitionDefaultValueNotAssignable)) == QualificationState.None;
        }

        internal bool InternalIsQualified(Plugin plugin)
        {
            return (plugin.QualificationState &
                    (QualificationState.QualificationMissing | QualificationState.PluginAssemblyCouldNotBeLoaded |
                     QualificationState.PluginNoExtensions)) == QualificationState.None;
        }

        internal bool InternalIsQualified(ExtensionType extensionType)
        {
            return (extensionType.QualificationState &
                    (QualificationState.QualificationMissing | QualificationState.ExtensionPluginNotQualified |
                     QualificationState.ExtensionTypeNotInPluginAssembly |
                     QualificationState.ExtensionInvalidConstructor | QualificationState.ExtensionNoPlugs)) ==
                   QualificationState.None;
        }

        internal bool InternalIsQualified(PlugType plugType)
        {
            return (plugType.QualificationState &
                    (QualificationState.QualificationMissing | QualificationState.PlugExtensionNotQualified |
                     QualificationState.PlugNameAlreadyExists | QualificationState.PlugSlotDefinitionNotFound |
                     QualificationState.PlugInterfaceNotImplemented | QualificationState.PlugParamMissing |
                     QualificationState.PlugParamNotQualified)) == QualificationState.None;
        }

        internal bool InternalIsQualified(Param param)
        {
            return (param.QualificationState &
                    (QualificationState.QualificationMissing | QualificationState.ParamPlugNotQualified |
                     QualificationState.ParamNameAlreadyExists | QualificationState.ParamNotAssignable)) ==
                   QualificationState.None;
        }

        internal bool InternalIsQualified(SlotType slotType)
        {
            return (slotType.QualificationState &
                    (QualificationState.QualificationMissing | QualificationState.SlotExtensionNotQualified |
                     QualificationState.SlotNameAlreadyExists | QualificationState.SlotSlotDefinitionNotFound)) ==
                   QualificationState.None;
        }

        internal bool InternalIsQualificationMissing(Qualifiable qualifiable)
        {
            return (qualifiable.QualificationState & QualificationState.QualificationMissing) ==
                   QualificationState.QualificationMissing;
        }

        internal bool InternalWeakQualify(Contract contract)
        {
            return InternalIsQualified(contract) ||
                   InternalIsQualificationMissing(contract) && InternalQualify(contract);
        }

        internal bool InternalWeakQualify(SlotDefinition slotDefinition)
        {
            return InternalIsQualified(slotDefinition) ||
                   InternalIsQualificationMissing(slotDefinition) &&
                   InternalQualify(slotDefinition);
        }

        internal bool InternalWeakQualify(ParamDefinition paramDefinition)
        {
            return InternalIsQualified(paramDefinition) ||
                   InternalIsQualificationMissing(paramDefinition) &&
                   InternalQualify(paramDefinition);
        }

        internal bool InternalWeakQualify(Plugin plugin)
        {
            return InternalIsQualified(plugin) ||
                   InternalIsQualificationMissing(plugin) && InternalQualify(plugin);
        }

        internal bool InternalWeakQualify(ExtensionType extensionType)
        {
            return InternalIsQualified(extensionType) ||
                   InternalIsQualificationMissing(extensionType) &&
                   InternalQualify(extensionType);
        }

        internal bool InternalWeakQualify(PlugType plugType)
        {
            return InternalIsQualified(plugType) ||
                   InternalIsQualificationMissing(plugType) && InternalQualify(plugType);
        }

        internal bool InternalWeakQualify(Param param)
        {
            return InternalIsQualified(param) ||
                   InternalIsQualificationMissing(param) && InternalQualify(param);
        }

        internal bool InternalWeakQualify(SlotType slotType)
        {
            return InternalIsQualified(slotType) ||
                   InternalIsQualificationMissing(slotType) && InternalQualify(slotType);
        }

        internal bool InternalQualify(Contract contract)
        {
            contract.QualificationState = QualificationState.None;
            OnQualifying("Contract", contract);

            if (contract.Assembly == null)
            {
                contract.Assembly = AssemblyAnalyzer.GetAssembly(contract.Uri.LocalPath);
                CheckContractAssemblyLoaded(contract);
            }

            CheckContractHasSlotDefinitions(contract);
            CheckUniqueContractName(contract);

            if (!InternalIsQualified(contract))
                return false;
            
            OnQualified("Contract", contract);
            
            return true;
        }

        private void CheckContractAssemblyLoaded(Contract contract)
        {
            if (!(contract.Assembly == null))
                return;

            OnError(
                "Contract",
                contract,
                string.Format(
                    Messages.Qualifier_AssemblyCouldNotBeLoaded,
                    contract.Uri));

            var cntrct = contract;
            var state = (long) (cntrct.QualificationState | QualificationState.ContractAssemblyCouldNotBeLoaded);
            cntrct.QualificationState = (QualificationState) state;
        }

        private void CheckContractHasSlotDefinitions(Contract contract)
        {
            if (contract.SlotDefinitions.Count != 0)
                return;
            
            OnError("Contract", contract, Messages.Qualifier_ContractHasNoSlotDefinitions);

            var cntrct = contract;
            var state = (long) (cntrct.QualificationState | QualificationState.ContractNoSlotDefinitions);
            cntrct.QualificationState = (QualificationState) state;
        }

        private void CheckUniqueContractName(Contract contract)
        {
            if (
                !TypeStore.InternalGetContracts(contract.Name).
                Any(other => other != contract && InternalIsQualified(other)))
                return;

            OnWarning(
                "Contract",
                contract,
                string.Format(
                    Messages.Qualifier_ElementAlreadyExists,
                    "Contract",
                    contract.Name));

            var cntrct = contract;
            var state = (long) (cntrct.QualificationState | QualificationState.ContractNameAlreadyExists);
            cntrct.QualificationState = (QualificationState) state;
        }

        internal bool InternalQualify(SlotDefinition slotDefinition)
        {
            slotDefinition.QualificationState = QualificationState.None;
            var contractQualified = InternalWeakQualify(slotDefinition.Contract);
            OnQualifying("Slot definition", slotDefinition);
            
            if (!CheckContractQualified(slotDefinition, contractQualified))
                return false;

            if (slotDefinition.Type == null)
            {
                slotDefinition.Type = slotDefinition.Contract.Assembly.GetType(slotDefinition.TypeName);
                CheckSlotDefinitionInterfaceDefined(slotDefinition);
            }
            else
                CheckSlotDefinitionInterfaceInContractAssembly(slotDefinition);

            CheckUniqueSlotDefinitionName(slotDefinition);

            if (!InternalIsQualified(slotDefinition))
                return false;

            OnQualified("Slot definition", slotDefinition);
            
            return true;
        }

        private bool CheckContractQualified(SlotDefinition slotDefinition, bool contractQualified)
        {
            if (contractQualified)
                return true;

            OnError(
                "Slot definition",
                slotDefinition,
                string.Format(
                    Messages.Qualifier_ElementIsNotQualified,
                    "Contract"));

            var slotDef = slotDefinition;
            var state = (long) (slotDef.QualificationState | QualificationState.SlotDefinitionContractNotQualified);
            slotDef.QualificationState = (QualificationState) state;
            
            return false;
        }

        private void CheckSlotDefinitionInterfaceDefined(SlotDefinition slotDefinition)
        {
            if (!(slotDefinition.Type == null))
                return;

            OnError(
                "Slot definition",
                slotDefinition,
                string.Format(
                    Messages.Qualifier_InterfaceNotInContractAssembly,
                    slotDefinition.TypeName,
                    slotDefinition.Contract.Assembly.FullName));
             
            var slotDef = slotDefinition;
            var state = (long) (slotDef.QualificationState | QualificationState.SlotDefinitionTypeNotInContractAssembly);
            slotDef.QualificationState = (QualificationState) state;
        }

        private void CheckSlotDefinitionInterfaceInContractAssembly(SlotDefinition slotDefinition)
        {
            if (!(slotDefinition.Type.Assembly != slotDefinition.Contract.Assembly) ||
                CheckAndUpdateReflectionOnlyAssembly(slotDefinition))
                return;

            OnError(
                "Slot definition",
                slotDefinition,
                string.Format(
                    Messages.Qualifier_InterfaceNotInContractAssembly,
                    slotDefinition.Type.FullName,
                    slotDefinition.Contract.Assembly.FullName));

            var slotDef = slotDefinition;
            var state = (long) (slotDef.QualificationState | QualificationState.SlotDefinitionTypeNotInContractAssembly);
            slotDef.QualificationState = (QualificationState) state;
        }

        private static bool CheckAndUpdateReflectionOnlyAssembly(SlotDefinition slotDefinition)
        {
            var typeAsm = slotDefinition.Type.Assembly;
            var contractAsm = slotDefinition.Contract.Assembly;

            if (typeAsm.FullName != contractAsm.FullName)
                return false;

            var updatedAsm = contractAsm.ReflectionOnly
                ? typeAsm
                : contractAsm;

            UpdateAssemblyReference(slotDefinition.Contract, updatedAsm);
            
            return true;
        }

        private void CheckUniqueSlotDefinitionName(SlotDefinition slotDefinition)
        {
            if (
                !TypeStore.InternalGetSlotDefinitions(slotDefinition.Name).
                Any(other => other != slotDefinition && InternalIsQualified(other)))
                return;

            OnError(
                "Slot definition",
                slotDefinition,
                string.Format(
                    Messages.Qualifier_ElementAlreadyExists,
                    "Slot definition",
                    slotDefinition.Name));
            
            var slotDef = slotDefinition;
            var state = (long) (slotDef.QualificationState | QualificationState.SlotDefinitionAlreadyExists);
            slotDef.QualificationState = (QualificationState) state;
        }

        internal bool InternalQualify(ParamDefinition paramDefinition)
        {
            paramDefinition.QualificationState = QualificationState.None;
            var slotDefinitionQualified = InternalWeakQualify(paramDefinition.SlotDefinition);
            OnQualifying("Param definition", paramDefinition);
            
            if (!CheckSlotDefinitionQualified(paramDefinition, slotDefinitionQualified))
                return false;
            
            if (paramDefinition.Type == null)
            {
                paramDefinition.Type = Type.GetType(paramDefinition.TypeName);
                CheckParamDefinitionTypeFound(paramDefinition);
            }
            
            if (paramDefinition.DefaultParam != null && paramDefinition.Type != null)
                CheckDefaultValueAssinable(paramDefinition);
            
            if (!InternalIsQualified(paramDefinition))
                return false;
            
            OnQualified("Param definition", paramDefinition);
            
            return true;
        }

        private bool CheckSlotDefinitionQualified(ParamDefinition paramDefinition, bool slotDefinitionQualified)
        {
            if (slotDefinitionQualified)
                return true;

            OnError(
                "Param definition",
                paramDefinition,
                string.Format(
                    Messages.Qualifier_ElementIsNotQualified,
                    "Slot definition"));

            var paramDef = paramDefinition;
            var state = (long) (paramDef.QualificationState | QualificationState.ParamDefinitionSlotDefinitionNotQualified);
            paramDef.QualificationState = (QualificationState) state;
            
            return false;
        }

        private void CheckParamDefinitionTypeFound(ParamDefinition paramDefinition)
        {
            if (!(paramDefinition.Type == null))
                return;

            OnError(
                "Param definition",
                paramDefinition,
                string.Format(
                    Messages.Qualifier_ParamDefinitionTypeNotFound,
                    paramDefinition.TypeName));

            var paramDef = paramDefinition;
            var state = (long) (paramDef.QualificationState | QualificationState.ParamDefinitionTypeNotFound);
            paramDef.QualificationState = (QualificationState) state;
        }

        private void CheckDefaultValueAssinable(ParamDefinition paramDefinition)
        {
            var obj = paramDefinition.DefaultParam.Value;

            if (obj == null)
            {
                if (!paramDefinition.Type.IsValueType)
                    return;
            }
            else if (paramDefinition.Type.IsInstanceOfType(obj))
                return;

            OnError(
                "Param definition",
                paramDefinition,
                string.Format(
                    Messages.Qualifier_ParamDefalutValueNotAssignable,
                    paramDefinition.Type.FullName));

            var paramDef = paramDefinition;
            var state = (long) (paramDef.QualificationState |
                QualificationState.ParamDefinitionDefaultValueNotAssignable);
            paramDef.QualificationState = (QualificationState) state;
        }

        internal bool InternalQualify(Plugin plugin)
        {
            plugin.QualificationState = QualificationState.None;
            OnQualifying("Plugin", plugin);

            if (plugin.Assembly == null)
            {
                plugin.Assembly = AssemblyAnalyzer.GetAssembly(plugin.Uri.LocalPath);
                CheckPluginAssemblyLoaded(plugin);
            }

            CheckPluginHasExtensions(plugin);
            CheckUniquePluginName(plugin);

            if (!InternalIsQualified(plugin))
                return false;

            OnQualified("Plugin", plugin);
            return true;
        }

        private void CheckPluginAssemblyLoaded(Plugin plugin)
        {
            if (!(plugin.Assembly == null))
                return;

            OnError(
                "Plugin",
                plugin,
                string.Format(
                    Messages.Qualifier_AssemblyCouldNotBeLoaded,
                    plugin.Uri));

            var plgn = plugin;
            var state = (long) (plgn.QualificationState | QualificationState.PluginAssemblyCouldNotBeLoaded);
            plgn.QualificationState = (QualificationState) state;
        }

        private void CheckPluginHasExtensions(Plugin plugin)
        {
            if (plugin.ExtensionTypes.Count != 0)
                return;
            
            OnError("Plugin", plugin, Messages.Qualifier_PluginHasNoExtensions);
            var plgn = plugin;
            var state = (long) (plgn.QualificationState | QualificationState.PluginNoExtensions);
            plgn.QualificationState = (QualificationState) state;
        }

        private void CheckUniquePluginName(Plugin plugin)
        {
            if (
                !TypeStore.InternalGetPlugins(plugin.Name).
                Any(other => other != plugin && InternalIsQualified(other)))
                return;

            OnWarning(
                "Plugin",
                plugin,
                string.Format(
                    Messages.Qualifier_ElementAlreadyExists,
                    "Plugin",
                    plugin.Name));

            var plgn = plugin;
            var state = (long) (plgn.QualificationState | QualificationState.PluginNameAlreadyExists);
            plgn.QualificationState = (QualificationState) state;
        }

        internal bool InternalQualify(ExtensionType extensionType)
        {
            extensionType.QualificationState = QualificationState.None;
            var pluginQualified = InternalWeakQualify(extensionType.Plugin);
            OnQualifying("Extension type", extensionType);
            
            if (!CheckPluginQualified(extensionType, pluginQualified))
                return false;
            
            if (extensionType.Type == null)
            {
                extensionType.Type = extensionType.Plugin.Assembly.GetType(extensionType.TypeName);
                CheckExtensionTypeDefined(extensionType);
            }
            else
                CheckExtensionTypeInPluginAssembly(extensionType);

            CheckExtensionConstructor(extensionType);
            CheckExtensionHasPlugs(extensionType);

            if (extensionType.Type != null)
                QualifyExtensionEventHandlers(extensionType);

            if (!InternalIsQualified(extensionType))
                return false;

            RegisterExtensionEventHandlers(extensionType);
            OnQualified("Extension type", extensionType);

            return true;
        }

        private bool CheckPluginQualified(ExtensionType extensionType, bool pluginQualified)
        {
            if (pluginQualified)
                return true;

            OnError(
                "Extension type",
                extensionType,
                string.Format(
                    Messages.Qualifier_ElementIsNotQualified,
                    "Plugin"));

            var extType = extensionType;
            var state = (long) (extType.QualificationState | QualificationState.ExtensionPluginNotQualified);
            extType.QualificationState = (QualificationState) state;
            
            return false;
        }

        private void CheckExtensionTypeDefined(ExtensionType extensionType)
        {
            if (!(extensionType.Type == null))
                return;

            OnError("Extension type", extensionType,
                    string.Format(
                        Messages.Qualifier_ClassNotInPluginAssembly,
                        extensionType.TypeName,
                        extensionType.Plugin.Assembly.FullName));

            var extType = extensionType;
            var state = (long) (extType.QualificationState | QualificationState.ExtensionTypeNotInPluginAssembly);
            extType.QualificationState = (QualificationState) state;
        }

        private void CheckExtensionTypeInPluginAssembly(ExtensionType extensionType)
        {
            if (!(extensionType.Type.Assembly != extensionType.Plugin.Assembly) ||
                CheckAndUpdateReflectionOnlyAssembly(extensionType))
                return;

            OnError(
                "Extension type",
                extensionType,
                string.Format(
                    Messages.Qualifier_ClassNotInPluginAssembly,
                    extensionType.Type.FullName,
                    extensionType.Plugin.Assembly.FullName));

            var extType = extensionType;
            var state = (long) (extType.QualificationState | QualificationState.ExtensionTypeNotInPluginAssembly);
            extType.QualificationState = (QualificationState) state;
        }

        private static bool CheckAndUpdateReflectionOnlyAssembly(ExtensionType extensionType)
        {
            var typeAsm = extensionType.Type.Assembly;
            var pluginAsm = extensionType.Plugin.Assembly;

            if (typeAsm.FullName != pluginAsm.FullName)
                return false;

            var updatedAsm = pluginAsm.ReflectionOnly
                ? typeAsm
                : pluginAsm;

            UpdateAssemblyReference(extensionType.Plugin, updatedAsm);

            return true;
        }

        private void CheckExtensionConstructor(ExtensionType extensionType)
        {
            if (!(extensionType.Type != null) || CheckConstructor(extensionType))
                return;

            OnError("Extension type", extensionType, Messages.Qualifier_InvalidConstructor);
            var extType = extensionType;
            var state = (long) (extType.QualificationState | QualificationState.ExtensionInvalidConstructor);
            extType.QualificationState = (QualificationState) state;
        }

        private void CheckExtensionHasPlugs(ExtensionType extensionType)
        {
            if (extensionType.PlugTypes.Count != 0 || extensionType == Runtime.StartupExtensionType)
                return;
            
            OnError("Extension type", extensionType, Messages.Qualifier_ExtensionHasNoPlugs);
            var extType = extensionType;
            var state = (long) (extType.QualificationState | QualificationState.ExtensionNoPlugs);
            extType.QualificationState = (QualificationState) state;
        }

        private void RegisterExtensionEventHandlers(ExtensionType extensionType)
        {
            foreach (
                var extension in
                    InstanceStore.InternalGetExtensions(extensionType).
                    Where(extension => InstanceStore.InternalIsActivated(extension)))
                Composer.AddExtensionReflectionHandlers(
                    extension, InstanceStore.GetExtensionObject(extension));
        }

        internal bool InternalQualify(PlugType plugType)
        {
            plugType.QualificationState = QualificationState.None;
            var extensionTypeQualified = InternalWeakQualify(plugType.ExtensionType);
            
            if (extensionTypeQualified)
                plugType.SlotDefinition =
                    TypeStore.InternalGetSlotDefinitions(plugType.Name).FirstOrDefault(InternalWeakQualify);

            OnQualifying("Plug type", plugType);

            if (!CheckExtensionTypeQualified(plugType, extensionTypeQualified))
                return false;

            CheckQualifiedSlotDefinitionFound(plugType);

            if (plugType.SlotDefinition != null)
            {
                CheckSlotInterfaceImplemented(plugType);
                CheckParamsDefined(plugType);
            }

            QualifyPlugEventHandlers(plugType);

            if (!InternalIsQualified(plugType))
                return false;

            RegisterPlugEventHandlers(plugType);
            OnQualified("Plug type", plugType);

            return true;
        }

        private bool CheckExtensionTypeQualified(PlugType plugType, bool extensionTypeQualified)
        {
            if (extensionTypeQualified)
                return true;

            OnError(
                "Plug type",
                plugType,
                string.Format(
                    Messages.Qualifier_ElementIsNotQualified,
                    "Extension type"));

            var plgType = plugType;
            var state = (long) (plgType.QualificationState | QualificationState.PlugExtensionNotQualified);
            plgType.QualificationState = (QualificationState) state;

            return false;
        }
         
        private void CheckQualifiedSlotDefinitionFound(PlugType plugType)
        {
            if (plugType.SlotDefinition != null)
                return;

            OnError(
                "Plug type",
                plugType,
                string.Format(
                    Messages.Qualifier_SlotDefinitionNotFound,
                    plugType.Name));

            var plgType = plugType;
            var state = (long) (plgType.QualificationState | QualificationState.PlugSlotDefinitionNotFound);
            plgType.QualificationState = (QualificationState) state;
        }

        private void CheckSlotInterfaceImplemented(PlugType plugType)
        {
            var extType = plugType.ExtensionType.Type;
            var slotInterface = plugType.SlotDefinition.Type;
            var interfaces = extType.GetInterfaces();
            
            if (interfaces.Any(@interface => @interface == slotInterface))
                return;

            var slotAsm = slotInterface.Assembly;

            if (extType.Assembly.ReflectionOnly != slotAsm.ReflectionOnly)
            {
                foreach (var intrface in interfaces.
                    Where(intrface => intrface.FullName == slotInterface.FullName))
                {
                    if (!slotAsm.ReflectionOnly)
                        return;

                    UpdateAssemblyReference(plugType.SlotDefinition.Contract, intrface.Assembly);
                    return;
                }
            }

            OnError(
                "Plug type",
                plugType,
                string.Format(
                    Messages.Qualifier_InterfaceNotImplemented,
                    slotInterface.FullName));

            var plgType = plugType;
            var state = (long) (plgType.QualificationState | QualificationState.PlugInterfaceNotImplemented);
            plgType.QualificationState = (QualificationState) state;
        }

        private void CheckParamsDefined(PlugType plugType)
        {
            foreach (var paramDef in plugType.SlotDefinition.
                ParamDefinitions.Where(InternalWeakQualify))
            {
                if (plugType.Params.Contains(paramDef.Name))
                {
                    if (!InternalWeakQualify(plugType.Params[paramDef.Name]))
                    {
                        OnError(
                            "Plug type",
                            plugType,
                            string.Format(
                                Messages.Qualifier_ParamNotQualified,
                                paramDef.Name));

                        var plgType = plugType;
                        var num = (long) (plgType.QualificationState | QualificationState.PlugParamNotQualified);
                        plgType.QualificationState = (QualificationState) num;
                    }
                }
                else if (paramDef.DefaultParam == null)
                {
                    OnError(
                        "Plug type",
                        plugType,
                        string.Format(
                            Messages.Qualifier_ParameterMissing,
                            paramDef.Name));

                    var plgType = plugType;
                    var state = (long) (plgType.QualificationState | QualificationState.PlugParamMissing);
                    plgType.QualificationState = (QualificationState) state;
                }
            }
        }

        private void RegisterPlugEventHandlers(PlugType plugType)
        {
            foreach (var extension in InstanceStore.InternalGetExtensions(
                plugType.ExtensionType).Where(extension => InstanceStore.InternalIsActivated(extension)))
                Composer.AddPlugReflectionHandlers(
                    extension.Plugs[plugType.Name], InstanceStore.GetExtensionObject(extension));
        }

        public bool InternalQualify(Param param)
        {
            param.QualificationState = QualificationState.None;
            var plugTypeQualified = InternalWeakQualify(param.PlugType);
            OnQualifying("Param", param);
            
            if (!CheckPlugTypeQualified(param, plugTypeQualified))
                return false;

            CheckParamAssignable(param);

            if (!InternalIsQualified(param))
                return false;
            
            OnQualified("Param", param);
            return true;
        }

        private bool CheckPlugTypeQualified(Param param, bool plugTypeQualified)
        {
            if (plugTypeQualified)
                return true;

            OnError(
                "Param",
                param,
                string.Format(
                    Messages.Qualifier_ElementIsNotQualified,
                    "Plug type"));

            var parm = param;
            var state = (long) (parm.QualificationState | QualificationState.ParamPlugNotQualified);
            parm.QualificationState = (QualificationState) state;
            return false;
        }

        private void CheckParamAssignable(Param param)
        {
            var paramDef = param.PlugType.SlotDefinition.ParamDefinitions[param.Name];

            if (paramDef == null || !InternalIsQualified(paramDef) ||
                paramDef.Type.IsInstanceOfType(param.Value))
                return;

            OnError(
                "Param",
                param,
                string.Format(
                    Messages.Qualifier_ParamValueNotAssignable,
                    paramDef.Type.FullName));

            var parm = param;
            var state = (long) (parm.QualificationState | QualificationState.ParamNotAssignable);
            parm.QualificationState = (QualificationState) state;
        }

        public bool InternalQualify(SlotType slotType)
        {
            slotType.QualificationState = QualificationState.None;
            var extensionTypeQualified = InternalWeakQualify(slotType.ExtensionType);
            
            if (extensionTypeQualified)
                slotType.SlotDefinition = TypeStore.InternalGetSlotDefinitions(
                    slotType.Name).FirstOrDefault(InternalWeakQualify);

            OnQualifying("Slot type", slotType);

            if (!CheckExtensionTypeQualified(slotType, extensionTypeQualified))
                return false;

            CheckQualifiedSlotDefinitionFound(slotType);
            QualifySlotEventHandlers(slotType);

            if (!InternalIsQualified(slotType))
                return false;

            RegisterSlotEventHandlers(slotType);
            OnQualified("Slot type", slotType);
            return true;
        }

        private bool CheckExtensionTypeQualified(SlotType slotType, bool extensionTypeQualified)
        {
            if (extensionTypeQualified)
                return true;

            OnError(
                "Slot type",
                slotType,
                string.Format(
                    Messages.Qualifier_ElementIsNotQualified,
                    "Extension type"));

            var sltType = slotType;
            var state = (long) (sltType.QualificationState | QualificationState.SlotExtensionNotQualified);
            sltType.QualificationState = (QualificationState) state;
            return false;
        }

        private void CheckQualifiedSlotDefinitionFound(SlotType slotType)
        {
            if (slotType.SlotDefinition != null)
                return;

            OnError(
                "Slot type",
                slotType,
                string.Format(
                    Messages.Qualifier_SlotDefinitionNotFound,
                    slotType.Name));

            var sltType = slotType;
            var state = (long) (sltType.QualificationState | QualificationState.SlotSlotDefinitionNotFound);
            sltType.QualificationState = (QualificationState) state;
        }

        private void RegisterSlotEventHandlers(SlotType slotType)
        {
            foreach (var extension in
                    InstanceStore.InternalGetExtensions(slotType.ExtensionType).
                    Where(extension => InstanceStore.InternalIsActivated(extension)))
                    Composer.AddSlotReflectionHandlers(extension.Slots[slotType.Name],
                    InstanceStore.GetExtensionObject(extension));
        }

        private void QualifyExtensionEventHandlers(ExtensionType extensionType)
        {
            var type = extensionType.Type;

            ExtensionType extType;
            long state;

            if (extensionType.OnCreated != null &&
                !SetMethodInfo(extensionType.OnCreated, type, ExtensionEventHandlerParamTypes))
            {
                OnWarning(
                    "Extension type",
                    extensionType,
                    string.Format(
                        Messages.Qualifier_EventHandlerMethodNotFound,
                        (object) "OnCreated",
                        (object) type.FullName,
                        (object) extensionType.OnCreated.Name,
                        (object) "ExtensionEventArgs"));

                extType = extensionType;
                state = (long) (extType.QualificationState |
                                QualificationState.ExtensionOnCreatedMethodNotFound);

                extType.QualificationState = (QualificationState) state;
            }

            if (extensionType.OnDestroyed == null ||
                SetMethodInfo(extensionType.OnDestroyed, type, ExtensionEventHandlerParamTypes))
                return;

            OnWarning(
                "Extension type",
                extensionType,
                string.Format(
                    Messages.Qualifier_EventHandlerMethodNotFound,
                    (object) "OnDestroyed",
                    (object) type.FullName,
                    (object) extensionType.OnDestroyed.Name,
                    (object) "ExtensionEventArgs"));

            extType = extensionType;
            state = (long) (extType.QualificationState |
                QualificationState.ExtensionOnDestroyedMethodNotFound);
            extType.QualificationState = (QualificationState) state;
        }

        private void QualifyPlugEventHandlers(PlugType plugType)
        {
            var type = plugType.ExtensionType.Type;

            PlugType plgType;
            long state;

            if (plugType.OnPlugged != null &&
                !SetMethodInfo(plugType.OnPlugged, type, CompositionEventHandlerParamTypes))
            {
                OnWarning(
                    "Plug type",
                    plugType,
                    string.Format(
                        Messages.Qualifier_EventHandlerMethodNotFound,
                        (object) "OnPlugged",
                        (object) type.FullName,
                        (object) plugType.OnPlugged.Name,
                        (object) "CompositionEventArgs"));

                plgType = plugType;
                state = (long) (plgType.QualificationState | QualificationState.PlugOnPluggedMethodNotFound);
                plgType.QualificationState = (QualificationState) state;
            }

            if (plugType.OnSelected != null &&
                !SetMethodInfo(plugType.OnSelected, type, CompositionEventHandlerParamTypes))
            {
                OnWarning(
                    "Plug type",
                    plugType,
                    string.Format(
                        Messages.Qualifier_EventHandlerMethodNotFound,
                        (object) "OnSelected",
                        (object) type.FullName,
                        (object) plugType.OnSelected.Name,
                        (object) "CompositionEventArgs"));

                plgType = plugType;
                state = (long) (plgType.QualificationState | QualificationState.PlugOnSelectedMethodNotFound);
                plgType.QualificationState = (QualificationState) state;
            }

            if (plugType.OnUnplugged != null &&
                !SetMethodInfo(plugType.OnUnplugged, type, CompositionEventHandlerParamTypes))
            {
                OnWarning(
                    "Plug type",
                    plugType,
                    string.Format(
                        Messages.Qualifier_EventHandlerMethodNotFound,
                        (object) "OnUnplugged",
                        (object) type.FullName,
                        (object) plugType.OnUnplugged.Name,
                        (object) "CompositionEventArgs"));

                plgType = plugType;
                state = (long) (plgType.QualificationState | QualificationState.PlugOnUnpluggedMethodNotFound);
                plgType.QualificationState = (QualificationState) state;
            }

            if (plugType.OnDeselected == null ||
                SetMethodInfo(plugType.OnDeselected, type, CompositionEventHandlerParamTypes))
                return;

            OnWarning(
                "Plug type",
                plugType,
                string.Format(
                    Messages.Qualifier_EventHandlerMethodNotFound,
                    (object) "OnDeselected",
                    (object) type.FullName,
                    (object) plugType.OnDeselected.Name,
                    (object) "CompositionEventArgs"));

            plgType = plugType;
            state = (long) (plgType.QualificationState | QualificationState.PlugOnDeselectedMethodNotFound);
            plgType.QualificationState = (QualificationState) state;
        }

        private void QualifySlotEventHandlers(SlotType slotType)
        {
            var type = slotType.ExtensionType.Type;

            long state;
            SlotType slotTyp;

            if (slotType.OnOpened != null &&
                !SetMethodInfo(slotType.OnOpened, type, SlotEventHandlerParamTypes))
            {
                OnWarning(
                    "Slot type",
                    slotType,
                    string.Format(
                        Messages.Qualifier_EventHandlerMethodNotFound,
                        (object) "OnOpened",
                        (object) type.FullName,
                        (object) slotType.OnOpened.Name,
                        (object) "SlotEventArgs"));

                slotTyp = slotType;
                state = (long) (slotTyp.QualificationState | QualificationState.SlotOnOpenedMethodNotFound);
                slotTyp.QualificationState = (QualificationState) state;
            }

            if (slotType.OnPlugged != null &&
                !SetMethodInfo(slotType.OnPlugged, type, CompositionEventHandlerParamTypes))
            {
                OnWarning(
                    "Slot type",
                    slotType,
                    string.Format(
                        Messages.Qualifier_EventHandlerMethodNotFound,
                        (object) "OnPlugged",
                        (object) type.FullName,
                        (object) slotType.OnPlugged.Name,
                        (object) "CompositionEventArgs"));

                slotTyp = slotType;
                state = (long) (slotTyp.QualificationState | QualificationState.SlotOnPluggedMethodNotFound);
                slotTyp.QualificationState = (QualificationState) state;
            }

            if (slotType.OnSelected != null &&
                !SetMethodInfo(slotType.OnSelected, type, CompositionEventHandlerParamTypes))
            {
                OnWarning(
                    "Slot type",
                    slotType,
                    string.Format(
                        Messages.Qualifier_EventHandlerMethodNotFound,
                        (object) "OnSelected",
                        (object) type.FullName,
                        (object) slotType.OnSelected.Name,
                        (object) "CompositionEventArgs"));

                slotTyp = slotType;
                state = (long) (slotTyp.QualificationState | QualificationState.SlotOnSelectedMethodNotFound);
                slotTyp.QualificationState = (QualificationState) state;
            }

            if (slotType.OnClosed != null &&
                !SetMethodInfo(slotType.OnClosed, type, SlotEventHandlerParamTypes))
            {
                OnWarning(
                    "Slot type",
                    slotType,
                    string.Format(
                        Messages.Qualifier_EventHandlerMethodNotFound,
                        (object) "OnClosed",
                        (object) type.FullName,
                        (object) slotType.OnClosed.Name,
                        (object) "SlotEventArgs"));

                slotTyp = slotType;
                state = (long) (slotTyp.QualificationState | QualificationState.SlotOnClosedMethodNotFound);
                slotTyp.QualificationState = (QualificationState) state;
            }

            if (slotType.OnUnplugged != null &&
                !SetMethodInfo(slotType.OnUnplugged, type, CompositionEventHandlerParamTypes))
            {
                OnWarning(
                    "Slot type",
                    slotType,
                    string.Format(
                        Messages.Qualifier_EventHandlerMethodNotFound,
                        (object) "OnUnplugged",
                        (object) type.FullName,
                        (object) slotType.OnUnplugged.Name,
                        (object) "CompositionEventArgs"));

                slotTyp = slotType;
                state = (long) (slotTyp.QualificationState | QualificationState.SlotOnUnpluggedMethodNotFound);
                slotTyp.QualificationState = (QualificationState) state;
            }

            if (slotType.OnDeselected == null ||
                SetMethodInfo(slotType.OnDeselected, type, CompositionEventHandlerParamTypes))
                return;
            OnWarning(
                "Slot type",
                slotType,
                string.Format(
                    Messages.Qualifier_EventHandlerMethodNotFound,
                    (object) "OnDeselected",
                    (object) type.FullName,
                    (object) slotType.OnDeselected.Name,
                    (object) "CompositionEventArgs"));

            slotTyp = slotType;
            state = (long) (slotTyp.QualificationState | QualificationState.SlotOnDeselectedMethodNotFound);
            slotTyp.QualificationState = (QualificationState) state;
        }

        private void OnQualifying(string elementKind, MetaElement element)
        {
            Logger.OnLog(VerbosityLevel.Diagnostic, this, Messages.Qualifier_ElementQualifying,
                              (object) elementKind, (object) element.Id, (object) element.Name);
        }

        private void OnWarning(string elementKind, MetaElement element, string message)
        {
            Logger.OnLog(VerbosityLevel.Normal, LogType.Warning, this,
                              Messages.Qualifier_WarningOnQualifying, (object) elementKind, (object) element.Id,
                              (object) element.Name, (object) message);
        }

        private void OnError(string elementKind, MetaElement element, string message)
        {
            Logger.OnLog(VerbosityLevel.Minimal, LogType.Error, this, Messages.Qualifier_ErrorOnQualifying,
                              (object) elementKind, (object) element.Id, (object) element.Name, (object) message);
        }

        private void OnQualified(string elementKind, MetaElement element)
        {
            Logger.OnLog(VerbosityLevel.Detailed, this, Messages.Qualifier_ElementQualified,
                              (object) elementKind, (object) element.Id, (object) element.Name);
        }

        private static bool CheckConstructor(ExtensionType extensionType)
        {
            var constructors =
                extensionType.Type.GetConstructors(
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
            var flag = false;

            foreach (var parameters in
                from MethodBase methodBase in constructors
                select methodBase.GetParameters())
            {
                if (CheckParameters(parameters, ConstructorParamTypes))
                    return extensionType.HasExtensionConstructor = true;

                if (parameters.Length == 0)
                    flag = true;
            }

            return flag;
        }

        private static bool SetMethodInfo(EventHandlerInfo eventHandlerInfo, Type type, Type[] parameterTypes)
        {
            foreach (var method in type.GetMethods(
                BindingFlags.Instance |
                BindingFlags.Static |
                BindingFlags.Public |
                BindingFlags.NonPublic).Where(
                method => CheckMethod(method, eventHandlerInfo.Name, parameterTypes)))
            {
                eventHandlerInfo.MethodInfo = method;
                return true;
            }

            return false;
        }

        private static bool CheckMethod(MethodInfo method, string name, Type[] parameterTypes)
        {
            return method.Name == name && CheckParameters(method.GetParameters(), parameterTypes);
        }

        private static bool CheckParameters(ParameterInfo[] parameters, Type[] parameterTypes)
        {
            return parameters.Length == parameterTypes.Length &&
                   !parameters.Where((t, index) =>
                                     t.ParameterType.FullName !=
                                     parameterTypes[index].FullName).Any();
        }

        internal static void UpdateAssemblyReference(Contract contract, Assembly assembly)
        {
            contract.Assembly = assembly;

            foreach (var slotDef in contract.SlotDefinitions)
                slotDef.Type = assembly.GetType(slotDef.TypeName);
        }

        internal static void UpdateAssemblyReference(Plugin plugin, Assembly assembly)
        {
            plugin.Assembly = assembly;

            foreach (var extType in plugin.ExtensionTypes)
            {
                var type = extType.Type = assembly.GetType(extType.TypeName);
                
                UpdateEventHandlerInfo(extType.OnCreated, type, ExtensionEventHandlerParamTypes);
                UpdateEventHandlerInfo(extType.OnDestroyed, type, ExtensionEventHandlerParamTypes);

                foreach (var plugType in extType.PlugTypes)
                {
                    UpdateEventHandlerInfo(plugType.OnPlugged, type, CompositionEventHandlerParamTypes);
                    UpdateEventHandlerInfo(plugType.OnSelected, type, CompositionEventHandlerParamTypes);
                    UpdateEventHandlerInfo(plugType.OnUnplugged, type, CompositionEventHandlerParamTypes);
                    UpdateEventHandlerInfo(plugType.OnDeselected, type, CompositionEventHandlerParamTypes);
                }

                foreach (var slotType in extType.SlotTypes)
                {
                    UpdateEventHandlerInfo(slotType.OnOpened, type, SlotEventHandlerParamTypes);
                    UpdateEventHandlerInfo(slotType.OnPlugged, type, CompositionEventHandlerParamTypes);
                    UpdateEventHandlerInfo(slotType.OnSelected, type, CompositionEventHandlerParamTypes);
                    UpdateEventHandlerInfo(slotType.OnClosed, type, SlotEventHandlerParamTypes);
                    UpdateEventHandlerInfo(slotType.OnUnplugged, type, CompositionEventHandlerParamTypes);
                    UpdateEventHandlerInfo(slotType.OnDeselected, type, CompositionEventHandlerParamTypes);
                }
            }
        }

        private static void UpdateEventHandlerInfo(EventHandlerInfo eventHandlerInfo, Type type, Type[] parameterTypes)
        {
            if (eventHandlerInfo == null || !(eventHandlerInfo.MethodInfo != null))
                return;
            
            SetMethodInfo(eventHandlerInfo, type, parameterTypes);
        }
    }
}

